/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }
  
  // scan index if there is one
  BTreeIndex btree;
  if ((rc = btree.open(table + ".idx", 'r')) == 0 && cond.size() > 0)
  {
	IndexCursor cursor;
	int min = 1;
	int max = 2147483647;
	bool minIncl = true;
	bool maxIncl = true;
	int equals = -1;
	bool conditionSet = false;
	count = 0;

	for (int i = 0; i < cond.size(); i++)
	{
		if (cond[i].attr == 1)			// if key
		{
			int value = atoi(cond[i].value);
			switch(cond[i].comp)
			{
				// EQ
				case 0: 
					if (equals != -1 && equals != value)
						return 0;
					else
						equals = value;
					conditionSet = true;
					break;
				
				// NE
				case 1: 
					break;
				
				// LT
				case 2: 
					if (max > value)
					{
						max = value;
						maxIncl = false;
					}
					conditionSet = true;
					break;
				
				// GT
				case 3: 
					if (min < value)
					{
						min = value;
						minIncl = false;
					}
					conditionSet = true;
					break;
				
				// LE
				
				case 4: 
					if (max >= value)
					{
						max = value;
						maxIncl = true;
					}
					conditionSet = true;
					break;
				
				// GE
				case 5: 
					if (min <= value)
					{
						min = value;
						minIncl = true;
					}
					conditionSet = true;
					break;
					
				default:
					break;
			}
		}
	}
	
	if (!conditionSet)
		goto noIndex;
	
	if (min > max)
		goto done;
  
	// if equals condition, find equivalent value
	if (equals > 0)
	{
		if (min > equals || max < equals)
			goto done;
			
		if ((rc = btree.locate(equals, cursor)) < 0)
			goto done;
		if ((rc = btree.readForward(cursor, key, rid)) < 0)
			goto done;
			
		// read the tuple
		if ((rc = rf.read(rid, key, value)) < 0) 
		{
			fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
			goto exit_select;
		}

		// check the conditions on the tuple
		for (unsigned i = 0; i < cond.size(); i++) 
		{
		  // compute the difference between the tuple value and the condition value
		  switch (cond[i].attr) 
		  {
			case 1:
				diff = key - atoi(cond[i].value);
				break;
			case 2:
				diff = strcmp(value.c_str(), cond[i].value);
				break;
		  }

		  // skip the tuple if any condition is not met
		  switch (cond[i].comp) 
		  {
			case SelCond::EQ:
				if (diff != 0) goto done;
				break;
			case SelCond::NE:
				if (diff == 0) goto done;
				break;
			case SelCond::GT:
				if (diff <= 0) goto done;
				break;
			case SelCond::LT:
				if (diff >= 0) goto done;
				break;
			case SelCond::GE:
				if (diff < 0) goto done;
				break;
			case SelCond::LE:
				if (diff > 0) goto done;
				break;
		  }
		}
		
		// the condition is met for the tuple. 
		// increase matching tuple counter
		count++;

		// print the tuple 
		switch (attr) {
		case 1:  // SELECT key
		  fprintf(stdout, "%d\n", key);
		  break;
		case 2:  // SELECT value
		  fprintf(stdout, "%s\n", value.c_str());
		  break;
		case 3:  // SELECT *
		  fprintf(stdout, "%d '%s'\n", key, value.c_str());
		  break;
		}

		// print matching tuple count if "select count(*)"
		if (attr == 4) {
			fprintf(stdout, "%d\n", count);
		}
		rc = 0;
		goto exit_select;
	}
	
	// otherwise, scan range
	else
	{
		if ((rc = btree.locate(min, cursor)) < 0)
			goto done;
		while ((btree.readForward(cursor, key, rid)) >= 0)
		{
			// check if we should be looking at this key or not
			if (key == min && !minIncl)
				continue;
			if (key == max && !maxIncl)
				continue;
			if (key <= 0)
				continue;
			if (key > max)
				break;
				
			// read the tuple
			if ((rc = rf.read(rid, key, value)) < 0) 
			{
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}

			// check the conditions on the tuple
			for (unsigned i = 0; i < cond.size(); i++) 
			{
			  // compute the difference between the tuple value and the condition value
			  switch (cond[i].attr) 
			  {
				case 1:
					diff = key - atoi(cond[i].value);
					break;
				case 2:
					diff = strcmp(value.c_str(), cond[i].value);
					break;
			  }

			  // skip the tuple if any condition is not met
			  switch (cond[i].comp) 
			  {
				case SelCond::EQ:
					if (diff != 0) continue;
					break;
				case SelCond::NE:
					if (diff == 0) continue;
					break;
				case SelCond::GT:
					if (diff <= 0) continue;
					break;
				case SelCond::LT:
					if (diff >= 0) continue;
					break;
				case SelCond::GE:
					if (diff < 0) continue;
					break;
				case SelCond::LE:
					if (diff > 0) continue;
					break;
			  }
			}
			
			// the condition is met for the tuple. 
			// increase matching tuple counter
			count++;

			// print the tuple 
			switch (attr) {
			case 1:  // SELECT key
			  fprintf(stdout, "%d\n", key);
			  break;
			case 2:  // SELECT value
			  fprintf(stdout, "%s\n", value.c_str());
			  break;
			case 3:  // SELECT *
			  fprintf(stdout, "%d '%s'\n", key, value.c_str());
			  break;
			}
		}
		
		done:
			// print matching tuple count if "select count(*)"
			if (attr == 4) {
				fprintf(stdout, "%d\n", count);
			}
			rc = 0;
			goto exit_select;
	}
  }
  
  // no index used, original code of function
  noIndex:
	  // scan the table file from the beginning
	  rid.pid = rid.sid = 0;
	  count = 0;
	  while (rid < rf.endRid()) {
		// read the tuple
		if ((rc = rf.read(rid, key, value)) < 0) {
		  fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
		  goto exit_select;
		}

		// check the conditions on the tuple
		for (unsigned i = 0; i < cond.size(); i++) {
		  // compute the difference between the tuple value and the condition value
		  switch (cond[i].attr) {
		  case 1:
		diff = key - atoi(cond[i].value);
		break;
		  case 2:
		diff = strcmp(value.c_str(), cond[i].value);
		break;
		  }

		  // skip the tuple if any condition is not met
		  switch (cond[i].comp) {
		  case SelCond::EQ:
		if (diff != 0) goto next_tuple;
		break;
		  case SelCond::NE:
		if (diff == 0) goto next_tuple;
		break;
		  case SelCond::GT:
		if (diff <= 0) goto next_tuple;
		break;
		  case SelCond::LT:
		if (diff >= 0) goto next_tuple;
		break;
		  case SelCond::GE:
		if (diff < 0) goto next_tuple;
		break;
		  case SelCond::LE:
		if (diff > 0) goto next_tuple;
		break;
		  }
		}

		// the condition is met for the tuple. 
		// increase matching tuple counter
		count++;

		// print the tuple 
		switch (attr) {
		case 1:  // SELECT key
		  fprintf(stdout, "%d\n", key);
		  break;
		case 2:  // SELECT value
		  fprintf(stdout, "%s\n", value.c_str());
		  break;
		case 3:  // SELECT *
		  fprintf(stdout, "%d '%s'\n", key, value.c_str());
		  break;
		}

		// move to the next tuple
		next_tuple:
		++rid;
	  }

	  // print matching tuple count if "select count(*)"
	  if (attr == 4) {
		fprintf(stdout, "%d\n", count);
	  }
	  rc = 0;

  // close the table file and return
  exit_select:
  btree.close();
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  string line;
  RecordFile rf;
  fstream file(loadfile.c_str());							// Open loadfile
  rf.open(table + ".tbl", 'w');								// Open or create table
  
  // Create B+ tree index
  BTreeIndex btree;
  if (index)
	btree.open(table + ".idx", 'w');
  
  if (file.is_open())
  {
	while(file.good())
	{
		int key;
		string value;
		RecordId rid;
		getline(file, line);								// Read from loadfile
		if (parseLoadLine(line, key, value) == 0)			// Parse into tuples
		{
			rf.append(key, value, rid);						// Append to table
			if (index)										// Add to B+ tree index
				btree.insert(key, rid);
		}
	}
	file.close();
  }
  else
	return RC_FILE_OPEN_FAILED;
	
  rf.close();
  if (index)
		btree.close();
  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
